package de.androidbuch.amando.services.impl;

import java.util.List;

import android.app.ProgressDialog;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Process;
import android.util.Log;
import de.androidbuch.amando.common.AmandoSmsUtil;
import de.androidbuch.amando.common.GeoMarkierung;
import de.androidbuch.amando.common.GpsData;
import de.androidbuch.amando.kontakt.GeoPosition;
import de.androidbuch.amando.kontakt.TelefonnummernHelfer;
import de.androidbuch.amando.services.IGeoPositionsService;

/**
 * @author Arno Becker, 2010 visionera gmbh
 */
public class GeoPositionsServiceImpl extends Service {
  
  /** Tag für die LogCat. */
  private static final String TAG =
    "GeoPositionsServiceImpl";

  /**
   * Minimaler Zeitraum zwischen zwei Abfragen der aktuellen
   * Ortsposition vom LocationManager.
   */
  private static int sMinZeit = 5000; // 5 Sekunden
  
  /**
   * Minimale Ortsveraenderung, ab der der Locationmanager
   * eine neue Ortsposition abfragt.
   */
  private static int sMinDistanz = 10; // 10 Meter

  /** Letzte bekannte eigene Geoposition. */
  private GpsData mGpsData;

  /**
   * Bei Positionsänderung die Position an den Amando-Server
   * schicken.
   */
  private boolean mPositionNachverfolgen;

  /** Mobilnummern der SMS-Empfänger. */
  private List<String> mEmpfaengerMobilnummern;

  /**
   * Das Stichwort des Senders, welches per SMS an den
   * Empfaenger geschickt wird.
   */
  private String mStichwort;

  /** 
   * Speichert die einmal ermittelte eigene 
   * Telefonnummer. 
   */
  private String mEigeneMobilnummer;

  /**
   * Das Flag wird immer dann auf true gesetzt, wenn die
   * 'Position senden'- Schaltfläche gedrückt wurde. Sobald
   * der Location-Listener eine Position ermittelt hat, wird
   * die SMS mit den Positionsdaten verschickt und, falls
   * die Position nachverfolgt werden soll, die Position an
   * den Server geschickt.
   */
  private volatile boolean mSendePositionFlag;
  
  /**
   * Dialog, der angezeigt wird, solange noch keine 
   * aktuelle (nicht älter als 3 Minuten) Position
   * vorliegt.
   */
  private ProgressDialog mPositionErmittelnDialog;

  /** Binder für Zugriff auf diesen Service. */
  private final IBinder mGpsBinder = 
    new GeoPositionsServiceBinder();
  
  /** Binder für Zugriff auf den Netzwerkservice. */
  private NetzwerkServiceImpl.NetzwerkServiceBinder 
      mNetzwerkBinder;
  
  /** LocationManager zur Positionermittlung. */
  private LocationManager mLocationManager;
  
  /** 
   * Listener lauscht auf Änderungen der eigenen Position. 
   */
  private LocationListener mLocationListener;

  /**
   * Listener lauscht auf Positionsveränderung.
   * @author Arno Becker
   *
   */
  private class MyLocationListener implements
      LocationListener {

    @Override
    public void onLocationChanged(final Location location) {
      Log.d(TAG, "MyLocationListener->" +
          "onLocationChanged(): entered...");
      if (location != null) {
        Log.d(TAG,
            "MyLocationListener->onLocationChanged(): " +
            "Längengrad: " + location.getLongitude());
        Log.d(TAG,
            "MyLocationListener->onLocationChanged(): " +
            "Breitengrad: " + location.getLatitude());
        mGpsData = new GpsData(location.getLongitude(),
            location.getLatitude(), location.getAltitude(),
            location.getTime());
  
        _sendeGeoPosition(true);        
        
        if (mPositionErmittelnDialog != null) {
          mPositionErmittelnDialog.dismiss();
        } 
      }
    }
    
    @Override
    public void onProviderDisabled(String provider) { }

    @Override
    public void onProviderEnabled(String provider) { }

    @Override
    public void onStatusChanged(String provider,
        int status, Bundle extras) { }
  };

  /**
   * Baut eine Verbindung zum lokalen Service auf. Der
   * Service laeuft im gleichen Prozess wie diese Activity.
   * Daher wird er automatisch beendet, wenn der Prozess der
   * Activity beendet wird.
   */
  private ServiceConnection mNetzwerkServiceConnection = 
      new ServiceConnection() {
    // Wird aufgerufen, sobald die Verbindung zum
    // Netzwerkservice steht.
    public void onServiceConnected(ComponentName className,
        IBinder binder) {
      Log.d(TAG, "netzwerkServiceConnection->" +
          "onServiceConnected(): " +
          "Verbindung mit Service steht...");
      mNetzwerkBinder = 
        (NetzwerkServiceImpl.NetzwerkServiceBinder) binder;
    }

    // Wird aufgerufen, sobald die Verbindung zum Service
    // unterbrochen wird.
    // Dies passiert nur, wenn der Prozess, der den Service
    // gestartet hat, stirbt.
    // Da dies ein lokaler Service ist, läuft er im selben
    // Prozess wie diese Activity.
    // Daher kann die Methode niemals aufgrufen werden und
    // muss nicht implementiert
    // werden.
    public void onServiceDisconnected(
        ComponentName className) {
      Log.d(TAG, "netzwerkServiceConnection->" +
          "onServiceDisconnected(): Verbindung mit " +
          "Service beendet...");
      // unerreichbar...
    }
  };

  /**
   * Klassen, die den Binder fuer den Zugriff von Clients
   * auf diesen Service definiert. Da dieser Service immer
   * im gleichen Prozess wie der Aufrufer laeuft, ist kein
   * IPC notwendig.
   */
  public class GeoPositionsServiceBinder extends Binder
      implements IGeoPositionsService {
    @Override
    public GpsData getGpsData() {
      return mGpsData;
    }
  
    @Override
    public void sendeGeoPosition(
        final List<String> empfaenger,
        final String stichwort, 
        final boolean positionAktualisieren,
        final ProgressDialog pd) {
      Log.d(TAG, "sendeGeoPosition(): Stichwort: " + 
          stichwort);
  
      mEmpfaengerMobilnummern = empfaenger;
      mStichwort = stichwort;
      mPositionNachverfolgen = positionAktualisieren;
      mSendePositionFlag = true;
  
      mEigeneMobilnummer = ermittleEigeneMobilnummer();
      
      // Warte auf Ermittlung der aktuellen Position:
      mPositionErmittelnDialog = pd;
  
      _sendeGeoPosition(false);
    }
  
    @Override
    public void setAnzeigeCallbackHandler(
        final Handler callback) {
      // Weitergabe an den Netzwerkserice. Dieser hat einen
      // Listener, der neue
      // Positionen von Clients empfaengt und aktualisiert
      // selber die
      // Oberflaeche:
      mNetzwerkBinder.setAnzeigeCallbackHandler(callback);
    }
  }

  @Override
  public void onCreate() {
    Log.d(TAG, "onCreate(): entered...");
    
    Log.d(TAG, "onCreate(): PID: " + Process.myPid());
    Log.d(TAG, "onCreate(): TID: " + Process.myTid());
    Log.d(TAG, "onCreate(): UID: " + Process.myUid());
  
    // verbinde mit Service zum Uebertragen der Geoposition:
    Log.d(TAG, "onCreate(): Verbindung zum " +
        "Netzwerkservice aufbauen...");
    final Intent netzwerkIntent = new Intent(this,
        NetzwerkServiceImpl.class);
    bindService(netzwerkIntent, mNetzwerkServiceConnection,
        Context.BIND_AUTO_CREATE);
    startService(netzwerkIntent);
 
    mLocationManager = (LocationManager) 
        getSystemService(Context.LOCATION_SERVICE);
    mLocationListener = new MyLocationListener();
    mLocationManager.requestLocationUpdates(
        LocationManager.GPS_PROVIDER,
        sMinZeit,
        sMinDistanz,
        mLocationListener);       
  }

  @Override
  public void onDestroy() {
    unbindService(mNetzwerkServiceConnection);
    stopService(new Intent(this,
        NetzwerkServiceImpl.class));
    Log.d(TAG, "onDestroy(): Service beenden...");
  }

  @Override
  public IBinder onBind(final Intent intent) {
    Log.d(TAG, "onBind(): entered...");
    return mGpsBinder;
  }

  /**
   * Verschickt die eigene Position per SMS (falls noch
   * nicht geschehen) an den Empfänger und anschließend an
   * den Amando-Server. Die eigene Position darf nicht älter
   * als 3 Minuten sein, sonst wird sie nicht gesendet.
   * 
   * @param sofort true, wenn die zuletzt bekannte Position
   *   sofort gesendet werden soll. false, wenn gewartet
   *   werden soll, bis eine neue aktuelle Position 
   *   ermittelt werden konnte.
   */
  private void _sendeGeoPosition(final boolean sofort) {
    // sende Position sofort (wenn die Methode aus
    // onLocationChanged
    // heraus aufgerufen wurde) oder wenn die Ortsposition
    // nicht
    // älter als 3 Minuten ist:
    final long vorDreiMinuten = 
      System.currentTimeMillis() - 180000L;
    if (sofort || (mGpsData != null &&
        mGpsData.mZeitstempel > vorDreiMinuten)) {
      if (mSendePositionFlag) {
        Log.d(TAG, 
            "_sendeGeoPosition(): " +
            "Sende SMS. Empfaengernummer: " + 
            mEmpfaengerMobilnummern);
        // an jeden Empfänger eine SMS schicken
        if (mEmpfaengerMobilnummern != null) {
          sendeGeoPositionPerSms();
        }
        mSendePositionFlag = false;
      }
      // letzte bekannte eigene Position an den
      // Amando-Server schicken, falls
      // die Clients (die Empfänger der SMS) die eigenen
      // Positionsänderungen
      // nachverfolgen dürfen:
      if (mPositionNachverfolgen) {
        sendeGeoPositionAnServer();
      }
    }
  }

  /** 
   * Schickt eine SMS an den Empfänger einer 
   * Geoposition. 
   */
  private void sendeGeoPositionPerSms() {
    Log.d(TAG,
        "sendeGeoPositionPerSms(): Schicke eine SMS an " +
        "die Empfänger der Geoposition");

    final GeoMarkierung geoMarkierung = new GeoMarkierung(
        mStichwort, mGpsData);

    if ("15555218135".equals(mEigeneMobilnummer)) {
   // Emulator:
      Log.d(TAG,
          "sendeGeoPositionPerSms(): Android-Geraet " +
          "ist: Emulator");
      AmandoSmsUtil.sendeSms(mEmpfaengerMobilnummern,
          geoMarkierung,
          mPositionNachverfolgen,
          AmandoSmsUtil.TYP_TEXT_SMS);
    } else { // echtes Gerät:
      Log.d(TAG,
          "sendeGeoPositionPerSms(): " +
          "Android-Geraet ist: Endgeraet");
      AmandoSmsUtil.sendeSms(mEmpfaengerMobilnummern,
          geoMarkierung,
          mPositionNachverfolgen,
          AmandoSmsUtil.TYP_DATEN_SMS);
    }
  }

  /** Schickt die aktuelle Position an den Amando-Server. */
  private void sendeGeoPositionAnServer() {
    Log.d(TAG,
        "sendeGeoPositionAnServer(): schicke die " +
        "aktuelle Position an den Amando-Server");
    final GeoPosition position = new GeoPosition(mGpsData,
        mEigeneMobilnummer);
    ((NetzwerkServiceImpl.NetzwerkServiceBinder) 
        mNetzwerkBinder).sendeEigenePosition(position);
  }

  /**
   * 
   * @return Die eigene Mobilnummer.
   */
  private String ermittleEigeneMobilnummer() {
    final String eigeneMobilnummer =
        TelefonnummernHelfer
        .ermittleEigeneMobilnummer(this);
    return TelefonnummernHelfer
        .bereinigeTelefonnummer(eigeneMobilnummer);
  }

}
